<!doctype html>
<meta charset="utf-8">
<title>Async Clipboard write should cancel the prior pending request</title>
<link rel="help" href="https://github.com/w3c/clipboard-apis/issues/161">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-actions.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<script src="resources/user-activation.js"></script>

<iframe width="50" height="50" id="same" src="resources/page.html"></iframe><br>
<iframe width="50" height="50" id="cross" src="https://{{hosts[alt][]}}:{{ports[https][1]}}/clipboard-apis/resources/page.html"></iframe><br>
<input value="Test">
<script>
"use strict";

// Permissions are required in order to invoke navigator.clipboard functions in
// an automated test.
async function getPermissions() {
  await tryGrantReadPermission();
  await tryGrantWritePermission()
  await waitForUserActivation();
}

function waitForMessage(msg) {
  return new Promise((resolve) => {
    window.addEventListener("message", function handler(e) {
      if (e.data == msg) {
        window.removeEventListener("message", handler);
        resolve();
      }
    });
  });
}

function generateRandomString() {
  return "random number: " + Math.random();
}

function testCancelPendingWrite(funcToMakeNewRequest, msg) {
  promise_test(async t => {
    await getPermissions();

    // Create a pending write request which should be rejected after a new
    // request is made.
    let resolvePendingPromise;
    let promise = navigator.clipboard.write([
      new ClipboardItem({
        "text/plain":  new Promise((resolve) => { resolvePendingPromise = resolve; }),
      }),
    ]).catch(e => { return e; });

    // Make a new request that should cancel the prior pending request.
    let str = generateRandomString();
    await funcToMakeNewRequest(str);

    // Pending request should be rejected with NotAllowedError.
    let error = await promise;
    assert_not_equals(error, undefined);
    assert_equals(error.name, "NotAllowedError");

    // Resolve pending promise.
    resolvePendingPromise(generateRandomString());
    // Check clipboard data.
    assert_equals(await navigator.clipboard.readText(), str);
  }, msg);
}

testCancelPendingWrite(async (str) => {
  // A new write request should cancel the prior pending request.
  await navigator.clipboard.write([
    new ClipboardItem({
      "text/plain":  str,
    }),
  ]).catch(() => {
    assert_true(false, "should not fail");
  });
}, "clipboard.write() should cancel the prior pending one (same document)");

testCancelPendingWrite(async (str) => {
  // A new write should cancel the prior pending request.
  const iframe = document.getElementById("same");
  iframe.contentWindow.postMessage(["write", str], "*");
  await waitForMessage("done");
}, "clipboard.write() should cancel the prior pending one (same-origin iframe)");

testCancelPendingWrite(async (str) => {
  // A new write should cancel the prior pending request.
  const iframe = document.getElementById("cross");
  iframe.contentWindow.postMessage(["write", str], "*");
  await waitForMessage("done");
}, "clipboard.write() should cancel the prior pending one (cross-origin iframe)");

testCancelPendingWrite(async (str) => {
  const input = document.querySelector("input");
  input.value = str;
  input.focus();
  input.select();

  // A new copy action should cancel the prior pending request.
  const modifier_key = navigator.platform.includes("Mac") ? "\uE03D" : "\uE009";
  await new test_driver.Actions().keyDown(modifier_key).keyDown("c").send();
}, "copy action should cancel the prior pending clipboard.write() request");

</script>
